1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.eventbus;
18
19 import com.google.common.base.Objects;
20 import com.google.common.base.Throwables;
21 import com.google.common.cache.CacheBuilder;
22 import com.google.common.cache.CacheLoader;
23 import com.google.common.cache.LoadingCache;
24 import com.google.common.collect.HashMultimap;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.Maps;
27 import com.google.common.collect.Multimap;
28 import com.google.common.reflect.TypeToken;
29 import com.google.common.util.concurrent.UncheckedExecutionException;
30
31 import java.lang.reflect.Method;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 import javax.annotation.Nullable;
38
39
40
41
42
43
44
45
46 class AnnotatedSubscriberFinder implements SubscriberFindingStrategy {
47
48
49
50
51
52
53 private static final LoadingCache<Class<?>, ImmutableList<Method>> subscriberMethodsCache =
54 CacheBuilder.newBuilder()
55 .weakKeys()
56 .build(new CacheLoader<Class<?>, ImmutableList<Method>>() {
57 @Override
58 public ImmutableList<Method> load(Class<?> concreteClass) throws Exception {
59 return getAnnotatedMethodsInternal(concreteClass);
60 }
61 });
62
63
64
65
66
67
68 @Override
69 public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) {
70 Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create();
71 Class<?> clazz = listener.getClass();
72 for (Method method : getAnnotatedMethods(clazz)) {
73 Class<?>[] parameterTypes = method.getParameterTypes();
74 Class<?> eventType = parameterTypes[0];
75 EventSubscriber subscriber = makeSubscriber(listener, method);
76 methodsInListener.put(eventType, subscriber);
77 }
78 return methodsInListener;
79 }
80
81 private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
82 try {
83 return subscriberMethodsCache.getUnchecked(clazz);
84 } catch (UncheckedExecutionException e) {
85 throw Throwables.propagate(e.getCause());
86 }
87 }
88
89 private static final class MethodIdentifier {
90 private final String name;
91 private final List<Class<?>> parameterTypes;
92
93 MethodIdentifier(Method method) {
94 this.name = method.getName();
95 this.parameterTypes = Arrays.asList(method.getParameterTypes());
96 }
97
98 @Override
99 public int hashCode() {
100 return Objects.hashCode(name, parameterTypes);
101 }
102
103 @Override
104 public boolean equals(@Nullable Object o) {
105 if (o instanceof MethodIdentifier) {
106 MethodIdentifier ident = (MethodIdentifier) o;
107 return name.equals(ident.name) && parameterTypes.equals(ident.parameterTypes);
108 }
109 return false;
110 }
111 }
112
113 private static ImmutableList<Method> getAnnotatedMethodsInternal(Class<?> clazz) {
114 Set<? extends Class<?>> supers = TypeToken.of(clazz).getTypes().rawTypes();
115 Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
116 for (Class<?> superClazz : supers) {
117 for (Method superClazzMethod : superClazz.getMethods()) {
118 if (superClazzMethod.isAnnotationPresent(Subscribe.class)
119 && !superClazzMethod.isBridge()) {
120 Class<?>[] parameterTypes = superClazzMethod.getParameterTypes();
121 if (parameterTypes.length != 1) {
122 throw new IllegalArgumentException("Method " + superClazzMethod
123 + " has @Subscribe annotation, but requires " + parameterTypes.length
124 + " arguments. Event subscriber methods must require a single argument.");
125 }
126
127 MethodIdentifier ident = new MethodIdentifier(superClazzMethod);
128 if (!identifiers.containsKey(ident)) {
129 identifiers.put(ident, superClazzMethod);
130 }
131 }
132 }
133 }
134 return ImmutableList.copyOf(identifiers.values());
135 }
136
137
138
139
140
141
142
143
144
145
146
147
148 private static EventSubscriber makeSubscriber(Object listener, Method method) {
149 EventSubscriber wrapper;
150 if (methodIsDeclaredThreadSafe(method)) {
151 wrapper = new EventSubscriber(listener, method);
152 } else {
153 wrapper = new SynchronizedEventSubscriber(listener, method);
154 }
155 return wrapper;
156 }
157
158
159
160
161
162
163
164
165
166 private static boolean methodIsDeclaredThreadSafe(Method method) {
167 return method.getAnnotation(AllowConcurrentEvents.class) != null;
168 }
169 }